//============================================================================
// Name        : webserver.cpp
// Author      : Muralidhar chouhan,Naveen Chintakindi,Naveen Kumar Kondoju
//============================================================================

#include <iostream>
#include <cstring>
#include <fstream>
#include <string>
#include "Queue.h"
#include "pthread.h"
#include "Table.h"
#include "Split.h"
#include <cstring>
#include "Cache.h"
#include "stdlib.h"
#include "unistd.h"
#include "time.h"
#include <gtk/gtk.h>
#include "X11/Xlib.h";
#include "WindowElement.h"

#define NUMWORKER 1

using namespace std;

class webserver {

	queue aqueue;
	string filename;
	table rtable;
	table atable;
	table ftable;
	pthread_mutex_t mutexdq;
	pthread_mutex_t mutexrq;
	pthread_mutex_t mutexaq;
	pthread_mutex_t mutexcache;
	pthread_mutex_t mutexrt;
	pthread_mutex_t mutexat;

public:
	string pwd;
	queue dqueue;
	queue rqueue;
	cachemem cache;

	void init() {

		pthread_mutex_init(&mutexdq, NULL);
		pthread_mutex_init(&mutexrq, NULL);
		pthread_mutex_init(&mutexaq, NULL);
		pthread_mutex_init(&mutexcache, NULL);
		pthread_mutex_init(&mutexat, NULL);
		pthread_mutex_init(&mutexrt, NULL);
		dqueue.setName("Dispatcher");
		rqueue.setName("Resolver");
		aqueue.setName("Authenticator");
		rtable.insert("A.txt", "courses/1001/A.txt");
		rtable.insert("C.txt", "courses/1002/C.txt");
		rtable.insert("B.txt", "courses/1001/B.txt");
		rtable.insert("D.txt", "courses/1002/D.txt");
		rtable.insert("E.txt", "courses/1003/E.txt");
		rtable.insert("F.txt", "courses/1003/F.txt");
		atable.insert("courses/1001/A.txt", "0.0.0.1");
		atable.insert("courses/1001/B.txt", "0.0.0.2");
		atable.insert("courses/1002/C.txt", "0.0.0.3");
		atable.insert("courses/1002/D.txt", "0.0.0.4");
		atable.insert("courses/1003/E.txt", "0.0.0.5");
		atable.insert("courses/1003/F.txt", "0.0.0.6");

	}
	void setFileName(string filenamer) {
		filename = filenamer;
	}

	void dispatcher(void) {
		ifstream in("webserver_inputs.txt");
		while (in) {
			std::string line;
			std::getline(in, line);
			if (line.size() != 0) {
				pthread_mutex_lock(&mutexdq);
				dqueue.enqueue(line);
				pthread_mutex_unlock(&mutexdq);
			}
		}
		pthread_mutex_lock(&mutexdq);
		dqueue.status = "FULL";
		pthread_mutex_unlock(&mutexdq);
		in.close();
	}
	void resolver(void) {
		int qlen = 0;
		char *cstr, *p;
		string loc = "";
		while (1) {
			pthread_mutex_lock(&mutexdq);
			qlen = dqueue.size();
			pthread_mutex_unlock(&mutexdq);
			if (qlen > 0 && !checkstatus(dqueue, &mutexdq, "EMPTY")) {
				pthread_mutex_lock(&mutexdq);
				string line = dqueue.dequeue();
				pthread_mutex_unlock(&mutexdq);
				split s;
				s.setRequest(line);
				if (s.getType() == "PUT" && s.getFiletype() == "FULL") {
					cstr = new char[s.getFilename().size() + 1];
					strcpy(cstr, s.getFilename().c_str());
					p = strtok(cstr, " ");
					string filename = "";
					while (p != NULL) {
						filename = p;
						p = strtok(NULL, " ");
					}
					rtable.insert(filename, s.getFilename());
				}
				if (s.getFiletype() == "PART") {
					if (s.getType() == "PUT")
						loc = rtable.search(s.getFilename());
					else
						loc = rtable.getsearch(s.getFilename());
				} else {
					loc = s.getFilename();
				}
				if (loc.compare("NO") != 0) {
					cstr = new char[loc.size() + 1];
					strcpy(cstr, loc.c_str());
					p = strchr(cstr, ',');
					if (p != NULL) {
						p = strtok(cstr, ",");
						while (p != NULL) {
							string newline = "";
							newline += s.getIp() + " " + s.getTimestamp() + " "
									+ s.getType() + " " + p;
							pthread_mutex_lock(&mutexrq);
							rqueue.enqueue(newline);
							pthread_mutex_unlock(&mutexrq);
							p = strtok(NULL, ",");
						}
					} else {
						string newline = "";
						newline += s.getIp() + " " + s.getTimestamp() + " "
								+ s.getType() + " " + loc;
						pthread_mutex_lock(&mutexrq);
						rqueue.enqueue(newline);
						pthread_mutex_unlock(&mutexrq);
					}

				} else {

					createWindowx(s,
							"The file does not exist or path does not exist",
							line);
				}
			} else if (checkstatus(dqueue, &mutexdq, "EMPTY")) {
				pthread_mutex_lock(&mutexrq);
				rqueue.status = "FULL";
				pthread_mutex_unlock(&mutexrq);
				break;
			}
		}
	}
	bool checkstatus(queue q, pthread_mutex_t *mut, string str) {
		string stat = "";
		pthread_mutex_lock(mut);
		stat = q.status;
		pthread_mutex_unlock(mut);
		if (stat == str)
			return true;
		else
			return false;

	}
	bool checkcache(cachemem q, pthread_mutex_t *mut, string str, string name) {
		string stat = "";
		pthread_mutex_lock(mut);
		stat = q.search(name);
		pthread_mutex_unlock(mut);
		if (stat == str)
			return true;
		else
			return false;
	}
	void SplitFilename(const string& str) {
		size_t found;
		cout << "Splitting: " << str << endl;
		found = str.find_last_of("/\\");
		cout << " folder: " << str.substr(0, found) << endl;
		cout << " file: " << str.substr(found + 1) << endl;
	}
	string fileCreate(split s) {
		string text = "";
		time_t rawtime;
		struct tm * timeinfo;
		time(&rawtime);
		timeinfo = localtime(&rawtime);
		string purefile = s.getFilename();
		SplitFilename(purefile);
		rtable.delet(purefile);
		rtable.insert(purefile, s.getFilename());
		atable.insert(s.getFilename(), s.getIp());
		text = "owner " + s.getIp() + "\n" + "Name " + s.getFilename()
				+ "\ncreated " + asctime(timeinfo) + "modified "
				+ asctime(timeinfo) + "\n";
		fileWrite(s.getFilename(), text);
		return text;
	}
	string fileRead(string name, int i) {
		if (!checkcache(cache, &mutexcache, "NO", name)) {
			pthread_mutex_lock(&mutexcache);
			string lin = cache.search(name);
			pthread_mutex_unlock(&mutexcache);
			return lin;
		}
		string line;
		string text = "";
		string modifytext = "";
		int f = 0;
		char *filename;
		name = pwd + "/" + name;
		filename = new char[name.size() + 1];
		strcpy(filename, name.c_str());
		ifstream myfile(filename);
		if (myfile.is_open()) {
			while (myfile.good()) {

				getline(myfile, line);
				cout << line;
				if (i == 1) {
					if (f != 3)
						modifytext += line + "\n";
					else {
						time_t rawtime;
						struct tm * timeinfo;

						time(&rawtime);
						timeinfo = localtime(&rawtime);
						modifytext += "modified ";
						modifytext += asctime(timeinfo);
						modifytext += "\n";
					}
				}
				text += '\n';
				text += line;
				f++;
			}
			myfile.close();
		}
		if (i == 1) {
			return modifytext;
		}
		return text;

	}
	bool fileExists(string name) {

		fstream file;
		char *filename;
		filename = new char[name.size() + 1];
		strcpy(filename, name.c_str());
		file.open(filename, ios_base::out | ios_base::in); // will not create file
		if (file.is_open()) {
			file.close();
			return true;
		}
		file.close();
		return false;

	}
	void fileWrite(string name, string content) {
		char *filename;
		filename = new char[name.size() + 1];
		strcpy(filename, name.c_str());
		ofstream myfile(filename);
		if (myfile.is_open()) {
			myfile << content;
			myfile.close();
		}
		pthread_mutex_lock(&mutexcache);
		cache.insert(name, content);
		pthread_mutex_unlock(&mutexcache);

	}

	void createWindowx(split sp, string content, string req) {
		Display *d;
		Window w;
		XEvent e;
		string printmsg = "\n" + req + "\n" + content + "\n";
		char *str;
		str = new char[printmsg.size() + 1];
		strcpy(str, printmsg.c_str());

		int s;
		d = XOpenDisplay(NULL);
		if (d == NULL) {
			fprintf(stderr, "Cannot open display\n");
			exit(1);
		}
		s = DefaultScreen(d);
		w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 500, 500, 1,
				BlackPixel(d, s), WhitePixel(d, s));
		XSelectInput(d, w, ExposureMask | KeyPressMask);
		XMapWindow(d, w);
		while (1) {
			XNextEvent(d, &e);
			if (e.type == Expose) {
				XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
				while (1) {
					char *cstr;
					cstr = new char[printmsg.size() + 1];
					strcpy(cstr, printmsg.c_str());
					char *p;
					p = strtok(cstr, "\n");
					int x = 10;
					int y = 50;
					while (p != NULL) {
						XDrawString(d, w, DefaultGC(d, s), x = x + 10,
								y = y + 10, p, strlen(p));
						p = strtok(NULL, "\n");
					}
					break;

				}
			}
			if (e.type == KeyPress)
				break;
		}
		XCloseDisplay(d);
	}
	void worker() {

		int qlen = 0;
		char *cstr, *p;
		//	cout << endl << "worker is called" << endl;
		string loc = "";
		while (1) {
			pthread_mutex_lock(&mutexaq);
			qlen = aqueue.size();
			pthread_mutex_unlock(&mutexaq);
			if (qlen > 0 && !checkstatus(aqueue, &mutexaq, "EMPTY")) {
				pthread_mutex_lock(&mutexaq);
				string line = aqueue.dequeue();
				pthread_mutex_unlock(&mutexaq);
				split s;
				s.setRequest(line);
				string ip = s.getIp();
				//	cout<<"looping all the way here"<<ftable.search(s.getFilename())<<endl;
				if (s.getType() == "PUT") {
					while (!(ftable.search(s.getFilename()).compare("NULL") == 0
							|| ftable.search(s.getFilename()).compare("NO") == 0))
						;
					//	cout << "looping" << endl;
					ftable.insert(s.getFilename(), "ACCESSED");
					cout << endl << "table accessed" << endl;
					if (fileExists(s.getFilename())) {
						cout << "file exits" << endl;
						string filecontent = fileRead(s.getFilename(), 1);
						fileWrite(s.getFilename(), filecontent);
						string purefile = s.getFilename();
						SplitFilename(purefile);
						rtable.delet(purefile);
						rtable.insert(purefile, s.getFilename());
						atable.insert(s.getFilename(), s.getIp());
						cout << filecontent << endl;
						createWindowx(s, filecontent, line);
						cout << "cameo out of the window";
					} else {
						string filecontent = fileCreate(s);
						cout << filecontent << endl;
						createWindowx(s, filecontent, line);
						cout << "came out of the window";
					}
					ftable.delet(s.getFilename());
				} else {
					while (!(ftable.search(s.getFilename()).compare("NULL") == 0
							|| ftable.search(s.getFilename()).compare("NO") == 0))
						;
					ftable.insert(s.getFilename(), "ACCESSED");
					string filecontent = fileRead(s.getFilename(), 0);
					cout << filecontent << endl;
					createWindowx(s, filecontent, line);
					cout << "came oute of the window";
					ftable.delet(s.getFilename());
				}

			} else if (checkstatus(aqueue, &mutexaq, "FULL")) {
				cout << "it is going to break";
				break;
			}
		}
	}

	void authenticator() {
		int qlen = 0;
		char *cstr, *p;
		string loc = "";
		while (1) {
			pthread_mutex_lock(&mutexrq);
			qlen = rqueue.size();
			pthread_mutex_unlock(&mutexrq);

			if (qlen > 0 && !checkstatus(rqueue, &mutexrq, "EMPTY")) {
				pthread_mutex_lock(&mutexrq);
				string line = rqueue.dequeue();
				pthread_mutex_unlock(&mutexrq);
				split s;
				s.setRequest(line);
				string ip = s.getIp();

				if (s.getType() == "PUT") {
					cout << "atable search for " << s.getIp() << s.getFilename()
							<< atable.search(s.getFilename());
					if (ip == atable.search(s.getFilename())
							|| atable.search(s.getFilename()).compare("NO") == 0) {
						pthread_mutex_lock(&mutexaq);
						aqueue.enqueue(line);
						pthread_mutex_unlock(&mutexaq);
						if (s.getFilename() == "PUT"
								&& s.getFiletype() == "FULL") {
							atable.insert(s.getFilename(), s.getIp());
						}
						atable.show();
					} else {
						cout << "you" << s.getIp()
								<< " are not authenticated to access file"
								<< s.getFilename();

						createWindowx(
								s,
								"You " + s.getIp()
										+ "are not authenticated to access file"
										+ s.getFilename(), line);
					}
				} else {
					pthread_mutex_lock(&mutexaq);
					aqueue.enqueue(line);
					pthread_mutex_unlock(&mutexaq);
				}

			} else if (checkstatus(rqueue, &mutexrq, "EMPTY")) {
				pthread_mutex_lock(&mutexaq);
				aqueue.status = "FULL";
				pthread_mutex_unlock(&mutexaq);
				break;
			}
		}
	}

	void stop() {
		pthread_mutex_destroy(&mutexdq);
		pthread_mutex_destroy(&mutexrq);
		pthread_mutex_destroy(&mutexaq);
		pthread_mutex_destroy(&mutexcache);
		pthread_mutex_destroy(&mutexat);
		pthread_mutex_destroy(&mutexrt);
		pthread_exit(NULL);
	}

};
void* dispatcher_helper(void *arg) {
	static_cast<webserver*>(arg)->dispatcher();
}
void* resolver_helper(void *arg) {
	static_cast<webserver*>(arg)->resolver();
}
void* authenticator_helper(void *arg) {
	static_cast<webserver*>(arg)->authenticator();
}
void* worker_helper(void *arg) {
	static_cast<webserver*>(arg)->worker();
}

int main(int argc, char* argv[]) {
	pthread_t dtid, rtid, atid;
	pthread_t wtid[NUMWORKER];
	pthread_attr_t attr;
	int drc, rrc, arc, wrc[NUMWORKER];
	void *status;
	webserver web;
	gtk_init(&argc, &argv);
	web.init();
	web.setFileName("webserver_inputs.txt");
	long size;
	char *buf;
	char *ptr;
	size = pathconf(".", _PC_PATH_MAX);
	if ((buf = (char *) malloc((size_t) size)) != NULL)
		ptr = getcwd(buf, (size_t) size);
	cout << endl << "pwd is" << buf;
	web.pwd = buf;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
	rrc = pthread_create(&rtid, &attr, resolver_helper, &web);
	drc = pthread_create(&dtid, &attr, dispatcher_helper, &web);
	arc = pthread_create(&atid, &attr, authenticator_helper, &web);
	for (int i = 0; i < NUMWORKER; i++) {
		wrc[i] = pthread_create(&wtid[i], &attr, worker_helper, &web);
	}
	pthread_attr_destroy(&attr);
	drc = pthread_join(dtid, &status);
	if (drc) {
		printf("ERROR; return code from pthread_create() is %d\n", drc);
		exit(-1);
	}
	cout << endl;

	rrc = pthread_join(rtid, &status);
	if (rrc) {
		printf("ERROR; return code from pthread_create() is %d\n", rrc);
		exit(-1);
	}
	arc = pthread_join(atid, &status);
	if (arc) {
		printf("ERROR; return code from pthread_create() is %d\n", arc);
		exit(-1);
	}
	for (int i = 0; i < NUMWORKER; i++) {
		wrc[i] = pthread_join(wtid[i], &status);
		if (wrc[i]) {
			printf("ERROR; return code from pthread_create() is %d\n", wrc[i]);
			exit(-1);
		}
	}
	web.dqueue.show();
	cout << endl;
	web.rqueue.show();
	cout << "terminating with status" << (long) status;
	cout << endl;
	web.stop();
	cout << "main server shuts down";
	return 0;
}
